﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;

public class MyTcpServerConn
{

    private bool Log_Enable = true;

    private Queue<SocketHelper.ThreadMessage> m_ThreadMessageQueue;

    private Socket m_Socket;
    private bool m_IsDispose;
    private int m_AcceptId = 0;
    private bool m_IsAccepting;

    private List<MyTcpAcceptConn> m_AcceptConnList = new List<MyTcpAcceptConn>();

    public MyTcpServerConn(Queue<SocketHelper.ThreadMessage> messageQueue)
    {
        m_ThreadMessageQueue = messageQueue;
    }

    public void StartServer()
    {
        if (null != m_Socket) return;
        
        var ipAddress = IPAddress.Parse("127.0.0.1");
        var ipPoint = new IPEndPoint(ipAddress, 8081);

        var so = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        m_Socket = so;
        so.Bind(ipPoint);
        so.Listen(5); //监听队列长度 (同时能够处理的连接请求数量, 0表示不限制)

        m_IsAccepting = true;
        so.BeginAccept(OnBeginAcceptResult, so);
    }

    public bool IsStarted
    {
        get { return null != m_Socket; }
    }

    //可以检测到有AcceptConn断开时, 重新开始Accept
    public void BeginAccepting()
    {
        if (m_IsAccepting) return;
        m_IsAccepting = true;

        var so = m_Socket;
        so.BeginAccept(OnBeginAcceptResult, so);
    }

    public bool IsAccepting
    {
        get { return m_IsAccepting; }
    }

    public void Close()
    {
        if (m_IsDispose) return;
        if (null == m_Socket) return;
        m_IsDispose = true;

        SocketHelper.Close(m_Socket);
        m_Socket = null;

        var list = m_AcceptConnList;
        lock (list)
        {
            foreach (var item in list)
            {
                item.Close();
            }
            list.Clear();
        }
    }

    public void AcceptConnDisconnect(MyTcpAcceptConn conn)
    {
        if (m_IsDispose) return;

        lock (m_AcceptConnList)
            m_AcceptConnList.Remove(conn);
    }

    public void Broadcast(string str)
    {
        var list = m_AcceptConnList;
        lock (list)
        {
            foreach (var item in list)
            {
                item.Send(str);
            }
        }
    }

    //******************** 异步线程

    private void OnBeginAcceptResult(IAsyncResult ar)
    {
        var socket = (Socket)ar.AsyncState;
        try
        {
            var clientSocket = socket.EndAccept(ar);
            if (Log_Enable) Debug.Log($"server: accepted: local:{clientSocket.LocalEndPoint}, remote:{clientSocket.RemoteEndPoint}");
            m_IsAccepting = false;

            MyTcpAcceptConn acceptConn = null;
            //~ Close执行到lock先拿到锁, 执行过程中, 这边没拿到锁等待, 然后再执行: 如果m_IsDispose因为线程变量缓存读到为false就会有问题
            //~ Close执行到lock, 但慢了一步后拿到锁, 这边先执行了, 同时m_IsDispose因为线程变量缓存读到为false: 则在Close拿到锁时, 还是能正确Close
            lock (m_AcceptConnList)
            {
                if (m_IsDispose)
                {
                    SocketHelper.ShutdownAndClose(clientSocket);
                }
                else
                {
                    acceptConn = new MyTcpAcceptConn(this, ++m_AcceptId, clientSocket, m_ThreadMessageQueue);
                    m_AcceptConnList.Add(acceptConn);
                }
            }

            if (null != acceptConn)
            {
                var msg = new SocketHelper.ThreadMessage();
                msg.m_Cmd = "Server.Accept";
                msg.m_Obj = acceptConn;
                EnqueueMessage(msg);
            }
        }
        catch (ObjectDisposedException ex)
        {
            Debug.LogWarning($"server: {ex.Message}");
        }
        catch (Exception ex)
        {
            Debug.LogError($"server: {ex.GetType().Name}:{ex.Message}");
        }
    }

    //******************** 

    private void EnqueueMessage(SocketHelper.ThreadMessage msg)
    {
        //渲染线程设为true前, 访问到了, 然后设为true了: 则渲染线程后续会读到失效连接的消息, 要紧吗?
        if (m_IsDispose)
        {
            Debug.Log($"server: disposed, Message discard: {msg.m_Cmd}");
            return;
        }

        lock (m_ThreadMessageQueue) //渲染线程, accpet线程, recv线程, send线程都会访问
        {
            m_ThreadMessageQueue.Enqueue(msg);
        }
    }

}
